#include "core/main.h"

namespace DodgeTheMissileConstants
{
    static const int gridWidth = 12;
    static const int gridHeight = 12;

    static const int playerSize = 16;

    static const UINT maxMissiles = 16;
    static const double missileSpawnRate = 0.8;

    static const UINT maxCandy = 1;
    static const double candySpawnRate = 0.1;

    static const int startX = 0;
    static const int startY = 0;

    static const int maxFramesPerEpisode = 5000;

    static const int penalty = 1000;
    static const int candyReward = 100;
}

void GameDodgeTheMissile::Init()
{
    _spritePlayer = _collection.AddSprite(Bitmap::LoadFile("../assets/dodgeTheMissile/player.png"), "player");
    _spriteMissile = _collection.AddSprite(Bitmap::LoadFile("../assets/dodgeTheMissile/missile.png"), "missile");
     _spriteCandy = _collection.AddSprite(Bitmap::LoadFile("../assets/dodgeTheMissile/candy.png"), "candy");

    _collection.AddFontSprites();

    _observedIntNames.push_back("Reset");
    _observedIntNames.push_back("Score");
    _observedIntNames.push_back("Frame score");
    _observedIntNames.push_back("Player x");
    _observedIntNames.push_back("Player y");
    _observedIntNames.push_back("Candy eaten");
    _observedIntNames.push_back("Frames since reset");

    _activeFrame.observedInts.resize(ObservedIntNames().size());
}

void GameDodgeTheMissile::NewGame()
{
    _state.score = 0;
    _state.playerX = DodgeTheMissileConstants::startX;
    _state.playerY = DodgeTheMissileConstants::startY;
    Reset();
}

void GameDodgeTheMissile::Reset()
{
    _state.missilesDodged = 0;
    _state.framesSinceReset = 0;
    _state.candyEaten = 0;
    _state.missiles.clear();
    _state.candies.clear();
}

void GameDodgeTheMissile::Step(ControllerState &controller)
{
    int frameScore = 0;

    // Check for player / missile intersections
    bool reset = false;
    {
        auto it = _state.missiles.begin();
        while (it != _state.missiles.end()) {
            Vec2i &missile = *it;
            if (missile.y == _state.playerY) {
                if (missile.x == _state.playerX) {
                    reset = true;
                    frameScore -= DodgeTheMissileConstants::penalty;
                } else {
                    _state.missilesDodged++;
                }
                it = _state.missiles.erase(it);
            } else {
                it->y--;
                ++it;
            }
        }
    }

    // Update candy positions
    {
        auto it = _state.candies.begin();
        while (it != _state.candies.end()) {
            Vec2i &candy = *it;
            if (candy.y == _state.playerY) {
                if (candy.x == _state.playerX) {
                    frameScore += DodgeTheMissileConstants::candyReward;
                    _state.candyEaten++;
                }
                it = _state.candies.erase(it);
            } else {
                it->y--;
                ++it;
            }
        }
    }

    // Update the player position
    if (controller.left()) {
        _state.playerX--;
    } else if (controller.right()) {
        _state.playerX++;
    }
    _state.playerX = Math::Bound(_state.playerX, 0, DodgeTheMissileConstants::gridWidth - 1);
    
    // Maybe spawn a missile
    int spawnedMissileX = -1;
    if (_state.missiles.size() < DodgeTheMissileConstants::maxMissiles) {
        if (rnd() < DodgeTheMissileConstants::missileSpawnRate) {
            int x = rand() % DodgeTheMissileConstants::gridWidth;
            spawnedMissileX = x;
            int y = DodgeTheMissileConstants::gridHeight - 1;
            _state.missiles.push_back(Vec2i(x, y));
        }
    }

    // Maybe spawn a candy, but not in the same place as a missile
    if (_state.candies.size() < DodgeTheMissileConstants::maxCandy) {
        if (rnd() < DodgeTheMissileConstants::candySpawnRate) {
            int x = rand() % DodgeTheMissileConstants::gridWidth;
            while (x == spawnedMissileX) {
                x = rand() % DodgeTheMissileConstants::gridWidth;
            }
            int y = DodgeTheMissileConstants::gridHeight - 1;
            _state.candies.push_back(Vec2i(x, y));
        }
    }

    _state.score += frameScore;
    _state.framesSinceReset++;

    if (_state.framesSinceReset == DodgeTheMissileConstants::maxFramesPerEpisode) {
        reset = true;
    }

    _activeFrame.observedInts[0] = reset ? 1 : 0;
    _activeFrame.observedInts[1] = _state.score;
    _activeFrame.observedInts[2] = frameScore;
    _activeFrame.observedInts[3] = _state.playerX;
    _activeFrame.observedInts[4] = _state.playerY;
    _activeFrame.observedInts[5] = _state.candyEaten;
    _activeFrame.observedInts[6] = _state.framesSinceReset;
        
    if (reset) {
        Reset();
    }

    MakeFrameSprites();
}

ControllerState GameDodgeTheMissile::SimpleAI()
{
    ControllerState controller;

    // Move randomly
    float num = rnd();
    if (num > 0.33f && num <= 0.4f) controller.KeyDown('a');
    else if (num > 0.66f && num <= 0.6f) controller.KeyDown('s');

    return controller;
}

void GameDodgeTheMissile::MakeFrameSprites()
{
    _activeFrame.sprites.clear();

    // Draw the player
    int screenPlayerX = _state.playerX * DodgeTheMissileConstants::playerSize;
    int screenPlayerY = (DodgeTheMissileConstants::gridHeight - _state.playerY - 1) * DodgeTheMissileConstants::playerSize;
    _activeFrame.sprites.push_back(SpriteLocation(Vec2i(screenPlayerX, screenPlayerY), _spritePlayer, 1));

    // Draw the missiles
    for (auto it = _state.missiles.begin(); it != _state.missiles.end(); ++it) {
        int screenMissileX = it->x * DodgeTheMissileConstants::playerSize;
        int screenMissileY = (DodgeTheMissileConstants::gridHeight - it->y - 1) * DodgeTheMissileConstants::playerSize;
        _activeFrame.sprites.push_back(SpriteLocation(Vec2i(screenMissileX, screenMissileY), _spriteMissile, 0));
    }

    // Draw the candy
    for (auto it = _state.candies.begin(); it != _state.candies.end(); ++it) {
        int screenCandyX = it->x * DodgeTheMissileConstants::playerSize;
        int screenCandyY = (DodgeTheMissileConstants::gridHeight - it->y - 1) * DodgeTheMissileConstants::playerSize;
        _activeFrame.sprites.push_back(SpriteLocation(Vec2i(screenCandyX, screenCandyY), _spriteCandy, 0));
    }

    // Draw the score
    _collection.DrawText("SCORE", Vec2i(24, 1), _activeFrame.sprites, 0);
    _collection.DrawNumber(_state.score, 5, Vec2i(24, 2), _activeFrame.sprites, 0);
}